import SwiftUI
import CoreBluetooth

let serviceUUID = CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
let charUUIDWrite = CBUUID(string: "6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
let charUUIDNotify = CBUUID(string: "6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
let notifyValue = Data(repeating: 1, count: 3)

class BLE: NSObject {
    private let centralManager = CBCentralManager()
    public private(set) var allPeripherals = [CBPeripheral]()
    public private(set) var connectedPeripheral: CBPeripheral?
    public private(set) var isConnected = false
    private var transferCharacteristic: CBCharacteristic? = nil
    private var notifyCharacteristic: CBCharacteristic? = nil
    private var writeCharacteristic: CBCharacteristic? = nil
    public var dataHandler: (([UInt8]) -> Void)? = nil
    
    public override init() {
        super.init()
        self.centralManager.delegate = self
        self.connectedPeripheral = nil
    }
    
    public func startScan() {
        centralManager.scanForPeripherals(withServices: nil, options: nil)
    }
    
    public func stopScan() {
        centralManager.stopScan()
    }
    
    public func clearAllDevices() {
        allPeripherals = []
    }
    
    public func scanServices() {
        if isConnected {
            connectedPeripheral!.discoverServices(nil)
        } else {
            print("Please connect ble device first!")
        }
    }
    
    public func readChar() {
        if isConnected {
            connectedPeripheral!.readValue(for: notifyCharacteristic!)
        } else {
            print("Please connect ble device first!")
        }
    }
    
    public func sendChar(data: Data) {
        if isConnected {
            //connectedPeripheral!.setNotifyValue(true, for: writeCharacteristic!)
            connectedPeripheral!.writeValue(data, for: writeCharacteristic!, type: .withoutResponse)
        } else {
            print("Please connect ble device first!")
        }
    }
    
    public func connectToPeripheral(deviceName: String) {
        let p = allPeripherals.first { cbPeripheral in
            return cbPeripheral.name! == deviceName
        }
        if p == nil {
            print("Device Not Found")
        } else {
            centralManager.connect(p!, options: nil)
            centralManager.stopScan()
        }
    }
    
    public func disconnect() {
        if isConnected {
            centralManager.cancelPeripheralConnection(connectedPeripheral!)
        } else {
            print("Currently you haven't connect to any device.")
        }
    }
}

extension BLE: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            print("Power On")
        case .poweredOff:
            print("Power Off")
        case .unknown:
            print("Unknown")
        default:
            print("other")
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if peripheral.name == nil { return }
        if allPeripherals.contains(peripheral) { return }
        allPeripherals.append(peripheral)
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("Successfully Connected to the device: \(peripheral.name!)")
        peripheral.delegate = self
        connectedPeripheral = peripheral
        connectedPeripheral?.delegate = self
        isConnected = true
        connectedPeripheral!.discoverServices(nil)
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("Failed to Connect to the device: \(peripheral.name!)")
        connectedPeripheral = nil
        isConnected = false
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        print("Disconnected to the device: \(peripheral.name!)")
        connectedPeripheral = nil
        isConnected = false
    }
}

extension BLE: CBPeripheralDelegate {
    // the transfer service was discovered
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let err = error {
            print("Error discovering services: \(err.localizedDescription)")
            return
        }
        print("Discover services...")
        guard let peripheralServices = peripheral.services else { return }
        
        for service in peripheralServices {
            if (service.uuid == serviceUUID) {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }
    
    // the transfer characteristic was discovered
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let err = error {
            print("Error discovering characteristics: \(err.localizedDescription)")
            return
        }
        print("Discovering characteristics...")
        guard let serviceCharacteristics = service.characteristics else { return }
        
        for characteristic in serviceCharacteristics {
            print(characteristic)
            if (characteristic.uuid == charUUIDNotify) {
                connectedPeripheral!.setNotifyValue(true, for: characteristic)
                print("write data")
                notifyCharacteristic = characteristic
            } else if (characteristic.uuid == charUUIDWrite) {
                writeCharacteristic = characteristic
                print("read data")
            }
        }
        // Once this is complete, we just need to wait for the data to come in.
    }
    
    // data has arrived via notification on the characteristic
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            print("Error discovering characteristic data: \(error!.localizedDescription)")
            return
        }
        
        guard let characteristicData = characteristic.value else {
            print("No value update...")
            return
        }
        
        if self.dataHandler != nil {
            self.dataHandler!([UInt8](characteristicData))
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if error != nil {
            print("Error Writing Characteristic: \(error!.localizedDescription)")
        }
        
        print("WriteValue Success!")
    }
    
    // when peripheral is ready to accept more data when using write without response
    func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
        if isConnected {
            print("Peripheral is ready, send data")
            connectedPeripheral?.writeValue(Data(repeating: 1, count: 3), for: transferCharacteristic!, type: .withoutResponse)
        }
    }
}

